package com.joysuch.wwyt.common.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.zlg.common.util.CollectionUtils;
import com.google.common.collect.Lists;
import com.joysuch.wwyt.bp.constant.AppNotifyContentPre;
import com.joysuch.wwyt.bp.entity.investigate.BpInvestigateTask;
import com.joysuch.wwyt.common.bean.NotifyQueryBean;
import com.joysuch.wwyt.common.cache.NotifyCache;
import com.joysuch.wwyt.common.constant.Constant;
import com.joysuch.wwyt.common.entity.CommonIndexNotify;
import com.joysuch.wwyt.common.entity.vo.CommonIndexNotifyVo;
import com.joysuch.wwyt.common.enums.AppBusinessNoticeTypes;
import com.joysuch.wwyt.common.mapper.AppBusinessNoticeMapper;
import com.joysuch.wwyt.common.mapper.CommonIndexNotifyMapper;
import com.joysuch.wwyt.common.repository.CommonIndexNotifyDao;
import com.joysuch.wwyt.common.service.CommonIndexNotifyService;
import com.joysuch.wwyt.config.RedisClient;
import com.joysuch.wwyt.core.common.ajax.ResultBean;
import com.joysuch.wwyt.core.common.util.DateTimeUtil;
import com.joysuch.wwyt.core.common.util.SimpleDateFormatCache;
import com.joysuch.wwyt.core.entity.BaseUser;
import com.joysuch.wwyt.core.enums.DeleteFlag;
import com.joysuch.wwyt.core.repository.BaseLoginAccountDao;
import com.joysuch.wwyt.core.repository.BaseUserDao;
import com.joysuch.wwyt.core.support.Context;
import com.joysuch.wwyt.edu.bean.CommonBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ScheduledFuture;
import java.util.stream.Collectors;

/**
 * @Author ZhangDong
 * @Date 2020/5/29 17:11
 */
@Service
@Slf4j
public class CommonIndexNotifyServiceImpl implements CommonIndexNotifyService {

    @Autowired
    private CommonIndexNotifyDao commonIndexNotifyDao;
    @Resource
    private ThreadPoolTaskScheduler taskScheduler;
    @Autowired
    private BaseUserDao baseUserDao;
    @Autowired
    private RedisClient redisClient;
    @Autowired
    private BaseLoginAccountDao loginAccountDao;
    @Autowired
    private AppBusinessNoticeMapper appNoticeMapper;
    @Autowired
    private CommonIndexNotifyMapper commonIndexNotifyMapper;
    private static final String TASK_SYNC = "task_sync";
    /**
     * 通知保存队列
     * 应对并发修改
     */
    private static final Queue<CommonIndexNotify> NOTIFY_SAVE_QUEUE = new ConcurrentLinkedDeque();

    /**
     * 生成通知任务
     */
    @Scheduled(initialDelay = 2002, fixedDelay = 60000L)
    public void todayTask() {

        generateNotifyTask();
    }


    @Override
    public void generateNotifyTask() {
        //查询当天+当天之前+没加入到通知队列
        List<CommonIndexNotify> list = commonIndexNotifyDao.findByRemindDateAndQueueOrNot(LocalDate.now());
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        for (CommonIndexNotify notify : list) {
            String key = getKey(notify);
            ScheduledFuture<?> future = taskScheduler.schedule(() -> {
                scheduleTaskBegin(notify, key);
            }, DateTimeUtil.localDateTime2Date(notify.getRemindDateTime()));
            //任务加入队列，以便cancel
            String resultMapKey = getMapKey(notify);
            NotifyCache.putFuture(resultMapKey, future, System.currentTimeMillis() + 2 * 24 * 60 * 60 * 1000);
        }
        list.forEach(r -> r.setQueueOrNot(1));
        //进行分批更新
        List<List<CommonIndexNotify>> lists = Lists.partition(list,1000);
        for (List<CommonIndexNotify> notifyList : lists){
            commonIndexNotifyDao.saveAll(notifyList);
        }
    }


    public void scheduleTaskBegin(CommonIndexNotify notify, String key) {
        synchronized (TASK_SYNC) {
            //缓存列队存储
//            String s = redisClient.get(key);
//            List<CommonIndexNotify> notifyFutureDtos = s == null ? Lists.newArrayList() : JSON.parseArray(s, CommonIndexNotify.class);
            notify.setRemindedOrNot(1);
            //替换缓存
            //redisClient.setEx(key, JSON.toJSONString(notifyFutureDtos), 5, TimeUnit.DAYS);
            NOTIFY_SAVE_QUEUE.offer(notify);
            //notifyFutureDtos.add(notify);
            //置队列时间位
            String resultMapKey = getKey(notify) + ":" + notify.getType() + ":" + notify.getId();
            NotifyCache.resetFurtureTime(resultMapKey);
            //log.info("定时任务执行人员:{},队列数据:{}", notify.getUserId(), JSON.toJSONString(notifyFutureDtos));
//            log.info("定时任务执行人员:{},notifyId:{}", notify.getUserId(), notify.getId());
        }
    }

    @Override
    public void cancelAndRemove(CommonIndexNotify notify) {
        String mapKey = getMapKey(notify);
        NotifyCache.cancelFuture(mapKey);
    }

    @PreDestroy
    public void resetNotify() {
        List<Long> longs = NotifyCache.needResetNotifyIds();
        List<CommonIndexNotify> list = commonIndexNotifyDao.findAllById(longs);
        Iterator<CommonIndexNotify> iterator = list.iterator();
        while (iterator.hasNext()) {
            CommonIndexNotify next = iterator.next();
            if (next.getRemindedOrNot() == 1) {
                iterator.remove();
            }
        }
        for (CommonIndexNotify notify : list) {
            notify.setQueueOrNot(0);
        }
        commonIndexNotifyDao.saveAll(list);
        log.info("消息通知，置未提醒任务初始位，ids:" + list.stream().map(r -> r.getId()).collect(Collectors.toList()));
    }

    @Scheduled(initialDelay = 3333, fixedDelay = 2000)
    public void daoSave() {
        List<CommonIndexNotify> list = Lists.newArrayList();
        CommonIndexNotify notify;
        while ((notify = NOTIFY_SAVE_QUEUE.poll()) != null) {
            CommonIndexNotify temp = new CommonIndexNotify();
            BeanUtils.copyProperties(notify, temp);
            list.add(temp);
        }
        if (!CollectionUtils.isEmpty(list)) {
            commonIndexNotifyDao.saveAll(list);
        }
    }

    private String getMapKey(CommonIndexNotify notify) {
        return getKey(notify) + ":" + notify.getType() + ":" + notify.getId();
    }

    //redis: k, List<notify>
    private String getKey(CommonIndexNotify notify) {
        return "message notify" + ":" + notify.getTenentId() + ":" + notify.getOrgCode().substring(0, 7) + ":" + notify.getUserId();
    }

    private String getKeyByCurrent() {
        Long currentUserId = Context.getCurrentUserId();
        String companyCode = Context.getCompanyCode();
        Long currentTenantId = Context.getCurrentTenantId();
        return "message notify" + ":" + currentTenantId + ":" + companyCode + ":" + currentUserId;
    }

    @Override
    public List<CommonIndexNotify> getByModelAndId(String modelName, Long modelId) {
        return commonIndexNotifyDao.findByTypeAndEduCertUserId(modelName, modelId);
    }

    @Override
    public List<CommonIndexNotify> getByTypeAndRecordId(String type, Long recordId) {
        return commonIndexNotifyDao.findByTypeAndRecordId(type, recordId);
    }

    @Override
    public void delete(Long id) {
        commonIndexNotifyDao.deleteById(id);
    }

    @Override
    public void saveList(List<CommonIndexNotify> notifyList) {
        commonIndexNotifyDao.saveAll(notifyList);
    }

    @Override
    public void deleteByTypeAndRecordId(String type, Long recordId) {
        commonIndexNotifyDao.deleteByTypeAndRecordId(type, recordId);
    }

    @Override
    @Transactional
    public ResultBean getNotify() {
        List<CommonIndexNotify> commonIndexNotifies = commonIndexNotifyDao.findByUserIdAndReadFlagAndRemindedOrNotAndDate(
                Context.getCurrentUserId(), LocalDateTime.now());
//        String s = redisClient.get(getKeyByCurrent());
//        if (s == null) {
//            return ResultBean.success(null);
//        }
//        List<CommonIndexNotify> commonIndexNotifies = JSON.parseArray(s, CommonIndexNotify.class);
        commonIndexNotifies.sort(Comparator.comparing(CommonIndexNotify::getCreateTime).reversed());
        return ResultBean.wrapSuccessfulResult(commonIndexNotifies);
    }

    //flag: 0 已读(单个，批量); 1 全部已读
    @Override
    @Transactional
    public ResultBean readed(List<Long> list, Integer flag) {
        List<CommonIndexNotify> indexNotifyList;
        if (flag == 0) {
            indexNotifyList = commonIndexNotifyDao.findAllById(list);
        } else {
            //已提醒未读
            indexNotifyList = commonIndexNotifyDao.findByRemindedOrNotAndReadFlag(1, 0);
        }
        for (CommonIndexNotify notify : indexNotifyList) {
            //修改表数据
            notify.setReadFlag(1);
            commonIndexNotifyDao.save(notify);
            String key = getKey(notify);
            NotifyCache.cancelFuture(key + ":" + notify.getType() + ":" + notify.getId());
            // 关联app消息中心已读状态同步变更
            if (AppBusinessNoticeTypes.INVESTIGATE.getType().equals(notify.getType())) {
                // 任务提醒，任务提醒中包含任务过期、排查异常、任务提醒。
                appNoticeMapper.updateReadStatusByRefIdAndTitle(notify.getEduCertUserId(), AppNotifyContentPre.TASK_NOTIFY, notify.getUserId());
            } else {
                appNoticeMapper.updateReadStatusByRefIdAndType(notify.getEduCertUserId(), notify.getType(), notify.getUserId());
            }
//            String s = redisClient.get(key);
//            if (s == null) {
//                continue;
//            }
//            List<CommonIndexNotify> commonIndexNotifies = JSON.parseArray(s, CommonIndexNotify.class);
//            if (CollectionUtils.isEmpty(commonIndexNotifies)) {
//                //如果redis挂掉
//                notify.setReadFlag(1);
//                commonIndexNotifyDao.save(notify);
//                continue;
//            }
//            Iterator<CommonIndexNotify> iterator = commonIndexNotifies.iterator();
//            while (iterator.hasNext()) {
//                CommonIndexNotify next = iterator.next();
//                if (notify.getId().equals(next.getId())) {
//                    //清缓存
//                    iterator.remove();
//                }
//            }
//            redisClient.setEx(key, JSON.toJSONString(commonIndexNotifies), 5, TimeUnit.DAYS);
        }
        return ResultBean.defaultSuccessResult();
    }

    @Override
    public void appRead(List<Long> refIds, String type, Long userId) {
        List<CommonIndexNotify> indexNotifyList;
        indexNotifyList = commonIndexNotifyDao.findByEduCertUserIdsAndType(refIds, type, userId);
        for (CommonIndexNotify notify : indexNotifyList) {
            notify.setReadFlag(1);
            commonIndexNotifyDao.save(notify);
            String key = getKey(notify);
            NotifyCache.cancelFuture(key + ":" + notify.getType() + ":" + notify.getId());
        }
    }

    @Override
    public void appDelete(List<Long> refIds, String type, Long userId) {
        List<CommonIndexNotify> indexNotifyList;
        indexNotifyList = commonIndexNotifyDao.findByEduCertUserIdsAndType(refIds, type, userId);
        for (CommonIndexNotify notify : indexNotifyList) {
            notify.setDeleteFlag(DeleteFlag.DELETED.getCode());
            commonIndexNotifyDao.save(notify);
            String key = getKey(notify);
            NotifyCache.cancelFuture(key + ":" + notify.getType() + ":" + notify.getId());
        }
    }

    @Override
    @Transactional
    public ResultBean page(Pageable pageable, NotifyQueryBean bean) {
        bean.setOrgCode(Context.getCompanyCode());
        bean.setTenentId(Context.getCurrentTenantId());
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<CommonIndexNotify> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(bean.getPage() + 1, bean.getSize());
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<CommonIndexNotify> pageData = commonIndexNotifyMapper.selectPageData(page,bean);
        List<CommonIndexNotify> content = pageData.getRecords();
        List<Long> userIdList = content.stream().map(r -> r.getUserId()).collect(Collectors.toList());
        ArrayList<CommonIndexNotifyVo> notifyVos = new ArrayList<>();
        SimpleDateFormat dateFormater = SimpleDateFormatCache.getYmdhm();
        Map<Long, String> map = baseUserDao.findAllById(userIdList).stream().collect(Collectors.toMap(r -> r.getId(), BaseUser::getRealName));
        for (CommonIndexNotify notify : content) {
            notify.setUserName(map.getOrDefault(notify.getUserId(), ""));
            CommonIndexNotifyVo notifyVo = new CommonIndexNotifyVo();
            BeanUtils.copyProperties(notify, notifyVo);
            notifyVo.setRemindDateTimeStr(dateFormater.format(DateTimeUtil.localDateTime2Date(notify.getRemindDateTime())));
            notifyVos.add(notifyVo);
        }
        return ResultBean.pageData(notifyVos, pageData.getTotal());
    }

    private static boolean isNumber(String s) {
        try {
            Long.parseLong(s);
        } catch (NumberFormatException e) {
            return false;
        }
        return true;
    }

    @Scheduled(initialDelay = 3214, fixedDelay = 86400000L)
    public void removeCache() {
        NotifyCache.removeExpireKey();
    }


    @Override
    @Scheduled(cron = "0 10 0 * * ?")
    public void generateNotifyJob() {
        Calendar today = Calendar.getInstance();
        today.set(Calendar.HOUR, 0);
        today.set(Calendar.MINUTE, 0);
        today.set(Calendar.SECOND, 0);
        today.add(Calendar.DATE, 1);
        LocalDate tomorrow = DateTimeUtil.date2LocalDate(today.getTime());
        List<CommonIndexNotify> notifyList = commonIndexNotifyDao.findRemindList(tomorrow);
        if (!notifyList.isEmpty()) {
            for (CommonIndexNotify notify : notifyList) {
                notify.setRemindedOrNot(1);
                notify.setQueueOrNot(1);
            }
            commonIndexNotifyDao.saveAll(notifyList);
        }
    }

    @Override
    public void releaseExamReminder(Set<Long> userIds, Long examId, String type, Date startTime, String msgBody, CommonBean commonBean) {
        Date date = new Date();
        /**
         * Date转LocalDateTime
         * */
        //获取系统默认是时区，如：Asia/Shanghai
        ZoneId zoneId = ZoneId.systemDefault();
        Instant instant = date.toInstant();
        //设置时区，把获取到的时间换算为当前设置的时区的时间
        LocalDateTime localDateTime = instant.atZone(zoneId).toLocalDateTime();
        //日期
        LocalDate localDate = localDateTime.toLocalDate();
        //web端发送信息
        List<CommonIndexNotify> commonIndexNotifyList = new ArrayList<>();
        for (Long userId : userIds) {
            CommonIndexNotify commonIndexNotify = new CommonIndexNotify();
            commonIndexNotify.setUserId(userId);
            commonIndexNotify.setOrgCode(commonBean.getOrgCode());
            commonIndexNotify.setTenentId(commonBean.getTenantId());
            //保存考试的id
            commonIndexNotify.setEduCertUserId(examId);
            commonIndexNotify.setType(type);
//            commonIndexNotify.setRemindDateTime(LocalDateTimeUtil.offset(localDateTime, 1, ChronoUnit.MONTHS));
            commonIndexNotify.setRemindDateTime(localDateTime);
            commonIndexNotify.setRemindDate(localDate);
            //设置是否提醒为已提醒
            commonIndexNotify.setRemindedOrNot(Constant.REMINDED);
            String realName = baseUserDao.getRealNameById(userId);
            String finalBody = MessageFormat.format(msgBody, realName);
            commonIndexNotify.setNotifyMessage(finalBody);
            commonIndexNotifyList.add(commonIndexNotify);
        }
        commonIndexNotifyDao.saveAll(commonIndexNotifyList);
    }

    @Override
    public void generateInvestigateTaskNotify(List<Long> userIds, String type, String msgBody, BpInvestigateTask task) {
        LocalDateTime localDateTime = DateUtil.toLocalDateTime(new Date());
        LocalDate localDate = localDateTime.toLocalDate();
        List<CommonIndexNotify> commonIndexNotifyList = new ArrayList<>();
        for (Long userId : userIds) {
            CommonIndexNotify commonIndexNotify = new CommonIndexNotify();
            commonIndexNotify.setUserId(userId)
                    .setType(type)
                    .setRemindDateTime(localDateTime)
                    .setRemindDate(localDate)
                    .setEduCertUserId(task.getId())
                    .setRemindedOrNot(Constant.REMINDED)
                    .setNotifyMessage(msgBody)
                    .setOrgCode(task.getOrgCode())
                    .setTenentId(task.getTenentId());
            commonIndexNotifyList.add(commonIndexNotify);
        }
        commonIndexNotifyDao.saveAll(commonIndexNotifyList);
    }

    @Override
    public boolean generateInvestigateTaskNotifyInRam(List<Long> userIds, String type, String msgBody, BpInvestigateTask task, List<CommonIndexNotify> list) {
        if (CollectionUtil.isEmpty(userIds)) {
            return false;
        }
        LocalDateTime localDateTime = DateUtil.toLocalDateTime(new Date());
        LocalDate localDate = localDateTime.toLocalDate();
        List<CommonIndexNotify> commonIndexNotifyList = new ArrayList<>();
        for (Long userId : userIds) {
            CommonIndexNotify commonIndexNotify = new CommonIndexNotify();
            commonIndexNotify.setUserId(userId)
                    .setType(type)
                    .setRemindDateTime(localDateTime)
                    .setRemindDate(localDate)
                    .setEduCertUserId(task.getId())
                    .setRemindedOrNot(Constant.REMINDED)
                    .setNotifyMessage(msgBody)
                    .setOrgCode(task.getOrgCode())
                    .setTenentId(task.getTenentId());
            commonIndexNotifyList.add(commonIndexNotify);
        }
        return list.addAll(commonIndexNotifyList);
    }

    @Override
    public boolean generateInvestigateTaskAssignNotifyInRam(List<Long> userIds, String type, String msgBody, BpInvestigateTask task, List<CommonIndexNotify> list) {
        if (CollectionUtil.isEmpty(userIds)) {
            return false;
        }

        LocalDateTime localDateTime = DateUtil.toLocalDateTime(new Date());
        LocalDate localDate = localDateTime.toLocalDate();
        List<CommonIndexNotify> commonIndexNotifyList = new ArrayList<>();

        for (Long userId : userIds) {
            CommonIndexNotify commonIndexNotify = new CommonIndexNotify();
            commonIndexNotify.setUserId(userId)
                    .setType(type)
                    .setRemindDateTime(localDateTime)
                    .setRemindDate(localDate)
                    .setEduCertUserId(task.getId())
                    .setRemindedOrNot(Constant.REMINDED)
                    .setNotifyMessage(msgBody)
                    .setOrgCode(task.getOrgCode())
                    .setTenentId(task.getTenentId());
            commonIndexNotifyList.add(commonIndexNotify);
        }
        return false;
    }

}
